# -*- coding: utf-8 -*-
"""
/***************************************************************************
 LandUseChangeDetectionDialog
                                 A QGIS plugin
 使用分类的方法检测土地利用变化
                             -------------------
        begin                : 2014-11-24
        copyright            : (C) 2014 by 许石罗
        email                : xushiluo@163.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""
import os
from itertools import product
import csv

from PyQt4 import QtCore, QtGui
from qgis.core import QgsVectorLayer

from osgeo import gdal
from osgeo.gdalconst import *

import numpy
import six
import pylab
from matplotlib import colors

from ui_landusechangedetection import Ui_LandUseChangeDetection
from LandClassifier import LandClassifier


class LandUseChangeDetectionDialog(QtGui.QDialog, Ui_LandUseChangeDetection):
    def __init__(self):
        QtGui.QDialog.__init__(self)

        self.setupUi(self)
        self.connectSignals()
        self.baseRoiFileName = None
        self.baseInFileName = None
        self.baseOutFileName = None
        self.secondRoiFileName = None
        self.secondInFileName = None
        self.secondOutFileName = None
        self.outputCsvFileName = None
        self.baseRoiFields = None
        self.secondRoiFields = None
        self.outputCSVFileName = None

    def connectSignals(self):
        self.BtnBaseInputImage.clicked.connect(self.showOpenBaseDialog)
        self.BtnBaseRoi.clicked.connect(self.showOpenBaseRoiDialog)

        self.BtnSecondInputImage.clicked.connect(self.showOpenSecondDialog)
        self.BtnSecondRoi.clicked.connect(self.showOpenSecondRoiDialog)

        self.BtnSaveCsv.clicked.connect(self.showSaveCsvDialog)

        self.comboBoxRoiBase.currentIndexChanged.connect(self.getBaseClassFieldName)
        self.comboBoxRoiSecond.currentIndexChanged.connect(self.getSecondClassFieldName)

        self.cancelButton.clicked.connect(self.close)
        self.okButton.clicked.connect(self.ChangeDetection)

    # 获取第一幅ROI文件的字段列表
    def getBaseClassFieldName(self, index):
        self.updateMemberData()  #更新类的数据成员
        baseVectorlayer = QgsVectorLayer(self.baseRoiFileName, "baseRoiLayer", "ogr")
        self.baseRoiFields = baseVectorlayer.dataProvider().fields()
        self.comboBoxBaseClassField.clear()
        for f in self.baseRoiFields:
           self.comboBoxBaseClassField.addItem(f.name(), f.name())

    # 获取第二幅ROI文件的字段列表
    def getSecondClassFieldName(self,index):
        self.updateMemberData()  #更新类的数据成员
        secondVectorlayer = QgsVectorLayer(self.secondRoiFileName, "secondVectorlayer", "ogr")
        self.secondRoiFields = secondVectorlayer.dataProvider().fields()
        self.comboBoxSecondClassField.clear()
        for f in self.secondRoiFields:
           self.comboBoxSecondClassField.addItem(f.name(),f.name())

    # 对话框显示时的事件处理函数
    def showEvent(self, event):
        super(LandUseChangeDetectionDialog, self).showEvent(event)

        #获取所有栅格图层
        rasterLayers = LandClassifier.getRasterLayers()
        items=[]
        for layer in rasterLayers:
            items.append((LandClassifier.getExtendedLayerName(layer), layer))

        #获取所有矢量图层
        vectorlayers = LandClassifier.getVectorLayers()
        vectorItems=[]
        for vectorLyr in vectorlayers:
            vectorItems.append((LandClassifier.getExtendedLayerName(vectorLyr), vectorLyr))

        # 先清空QComboBox的所有对象
        self.comboBoxInputBase.clear()
        self.comboBoxRoiBase.clear()
        self.comboBoxInputSecond.clear()
        self.comboBoxRoiSecond.clear()

        #将所以栅格图层添加到栅格图像列表中
        for (name, value) in items:
            self.comboBoxInputBase.addItem(name, value)
        for (name, value) in items:
            self.comboBoxInputSecond.addItem(name, value)

        #将所有矢量图层添加到roi列表中
        for (name, value) in vectorItems:
            self.comboBoxRoiBase.addItem(name, value)
        for (name, value) in vectorItems:
            self.comboBoxRoiSecond.addItem(name, value)

    #读取基准栅格图像
    def showOpenBaseDialog(self):
        fileName = str(QtGui.QFileDialog.getOpenFileName(self,
                                                        "Input Raster File:", "",
                                                        "Tiff Image (*.tif *.tiff);;Jpg Image (*.jpg)" ) )
        if len(fileName) is 0:
            return
        else:
            self.baseInFileName = fileName

        self.comboBoxInputBase.addItem(self.baseInFileName,self.baseInFileName)
        self.comboBoxInputBase.setCurrentIndex(self.comboBoxInputBase.count() - 1)

    #读取基准ROI区域
    def showOpenBaseRoiDialog(self):
        fileName = str(QtGui.QFileDialog.getOpenFileName(self, "Input Raster File:", "",
                                                        "Shape Image(*.shp)") )
        if len(fileName) is 0:
            return
        else:
            self.baseRoiFileName = fileName

        self.comboBoxRoiBase.addItem(self.baseRoiFileName, self.baseRoiFileName)
        self.comboBoxRoiBase.setCurrentIndex(self.comboBoxRoiBase.count() - 1)

    #读取第二幅栅格图像
    def showOpenSecondDialog(self):
        fileName = str(QtGui.QFileDialog.getOpenFileName(self,
                                                        "Input Raster File:", "",
                                                        "Tiff Image (*.tif *.tiff)" ) )
        if len(fileName) is 0:
            return
        else:
            self.secondInFileName = fileName

        self.comboBoxInputSecond.addItem(self.secondInFileName,self.secondInFileName)
        self.comboBoxInputSecond.setCurrentIndex(self.comboBoxInputSecond.count() - 1)

    #读取第二幅栅格图像roi区域
    def showOpenSecondRoiDialog(self):
        fileName = str(QtGui.QFileDialog.getOpenFileName(self, "Input Raster File:", "",
                                                        "Shape Image(*.shp)") )
        if len(fileName) is 0:
            return
        else:
            self.secondRoiFileName = fileName

        self.comboBoxRoiSecond.addItem(self.secondRoiFileName, self.secondRoiFileName)
        self.comboBoxRoiSecond.setCurrentIndex(self.comboBoxRoiSecond.count() - 1)

    # 保存变化结果为csv文件对话框
    def showSaveCsvDialog(self):
        fileTypes = 'CSV/Text Files (*.csv *.txt)'
        fileName, filter = QtGui.QFileDialog.getSaveFileNameAndFilter(
            self, 'Output CSV File:', '', fileTypes)
        if len(fileName) is 0:
            return
        else:
            # Extract the base filename without the suffix if it exists
            # Convert the fileName from QString to python string
            fileNameStr = str(fileName)

            # Split the fileNameStr where/if a '.' exists
            splittedFileName = fileNameStr.split('.')

            # Finally extract the base filename from the splitted filename
            baseFileName = splittedFileName[0]

            # Initialize the suffix string
            suffixStr = ''

            # Check if the user entered a suffix
            suffixExists = False
            existingSuffix = ''
            if len(splittedFileName) != 1:
                existingSuffix = splittedFileName[len(splittedFileName) - 1]
                if existingSuffix is not None:
                    suffixExists = True


            # Extract the suffix from the selected filetype filter
            # Convert the selected filter from QString to python string
            filterStr = str(filter)

            # Split the filter string where/if an asterisk (*) exists
            # I do this to find where the first suffix of the selected filetype
            # occurs
            splittedFilter = filterStr.split('*')

            # If a suffix is not supplied by the user it will be automatically
            # added to the filename. The default suffix will be the first
            # available suffix for the chosen filetype
            if not suffixExists:
                # Extract the 'dirty' suffix string where the first suffix is located
                dirtySuffixStr = splittedFilter[1]

                # Find out the number of the available suffixes
                suffixNum = len(splittedFilter) - 1

                if suffixNum == 1:
                    # Split the dirty suffix string where a ')' occurs
                    # which indicates where the selected filetype ends
                    splittedDirtySuffixStr = dirtySuffixStr.split(')')
                else:
                    # Split the dirty suffix string where a space occurs which
                    # indicates where the selected filetype suffix ends
                    splittedDirtySuffixStr = dirtySuffixStr.split(' ')
                suffixStr = splittedDirtySuffixStr[0]
            else:
                # WE NEED TO CHECK IF THE SUPPLIED SUFFIX CORRESPONDS TO THE
                # SELECTED FILETYPE

                # Extract all the suffixes available for the selected filetype
                # First find out the number of the available suffixes
                suffixNum = len(splittedFilter) - 1

                if suffixNum == 1:
                    # Extract the 'dirty' suffix string where the suffix is located
                    dirtySuffixStr = splittedFilter[1]

                    # Split the dirty suffix string where a space occurs which
                    # indicates where the selected filetype suffix ends
                    splittedDirtySuffixStr = dirtySuffixStr.split(' ')
                    suffixStr = splittedDirtySuffixStr[0]
                else:
                    suffixList = []
                    if suffixNum == 2:
                        # Extract the first suffix and put it in the list
                        dirtySuffixStr = splittedFilter[1]
                        splittedDirtySuffixStr = dirtySuffixStr.split(' ')
                        suffixList.append(splittedDirtySuffixStr[0])

                        # Extract the second suffix and put it in the list
                        dirtySuffixStr = splittedFilter[2]
                        splittedDirtySuffixStr = dirtySuffixStr.split(')')
                        suffixList.append(splittedDirtySuffixStr[0])

                    else:
                        # Extract the first suffix and put it in the list
                        dirtySuffixStr = splittedFilter[1]
                        splittedDirtySuffixStr = dirtySuffixStr.split(' ')
                        suffixList.append(splittedDirtySuffixStr[0])

                        # Extract the last suffix and put it in the list
                        dirtySuffixStr = splittedFilter[suffixNum]
                        splittedDirtySuffixStr = dirtySuffixStr.split(')')
                        suffixList.append(splittedDirtySuffixStr[0])

                        # Extract the rest of the suffixes and put them in the list
                        for i in xrange(2, suffixNum):
                            dirtySuffixStr = splittedFilter[i]
                            splittedDirtySuffixStr = dirtySuffixStr.split(' ')
                            suffixList.append(splittedDirtySuffixStr[0])

                    # Find if the user supplied suffix is valid for the
                    # chosen filetype and set it as the filename suffix
                    isValidSuffix = False
                    userSuffix = '.' + existingSuffix
                    for i in xrange(suffixNum + 1):
                        if userSuffix == suffixList[i]:
                            isValidSuffix = True
                            suffixStr = userSuffix
                            break

                    # If the supplied suffix is not valid replace it
                    # with the default suffix for the chosen filetype
                    if not isValidSuffix:
                        suffixStr = suffixList[0]

            self.outputCsvFileName = baseFileName + suffixStr

        self.lineEditSaveCsv.clear()
        self.lineEditSaveCsv.setText(self.outputCsvFileName)

    # 从界面更新类的数据成员
    def updateMemberData(self):
        self.baseInFileName = LandClassifier.getInputLayerPath( self.comboBoxInputBase.itemData(self.comboBoxInputBase.currentIndex()) )
        self.baseRoiFileName = LandClassifier.getInputLayerPath( self.comboBoxRoiBase.itemData(self.comboBoxRoiBase.currentIndex()) )
        self.secondInFileName = LandClassifier.getInputLayerPath(self.comboBoxInputSecond.itemData(self.comboBoxInputSecond.currentIndex()))
        self.secondRoiFileName = LandClassifier.getInputLayerPath(self.comboBoxRoiSecond.itemData(self.comboBoxRoiSecond.currentIndex()))
        self.outputCSVFileName = self.lineEditSaveCsv.text()

    #执行分类操作
    def ExecClassify(self, inputImageFileName, shapeRoiFileName, outputFileName,classFieldName="class"):
        # 1. 计算图像的统计信息，生成一个xml文件
        computeImagesStatisticsFileName = LandClassifier.getComputeImagesStatisticsFileName(inputImageFileName)
        computeImagesStatisticsReText = LandClassifier.execComputeImagesStatistics(inputImageFileName, computeImagesStatisticsFileName)
        #self.plainTextEditResult.setPlainText( self.plainTextEditResult.toPlainText() + computeImagesStatisticsReText )

        # 2.构建分类器模型，生成一个txt文件
        bayesModelTxtFileName = LandClassifier.getClassifierModelName(inputImageFileName)
        bayesConfusionMatrixFileName = LandClassifier.getConfusionMatrixName(inputImageFileName)
        buildBayesReText = LandClassifier.execBayesClassfier(inputImageFileName,
                                                                      shapeRoiFileName,
                                                                      computeImagesStatisticsFileName,
                                                                      bayesModelTxtFileName,
                                                                      bayesConfusionMatrixFileName,
                                                                      classFieldName
                                                                      )
        #self.plainTextEditResult.setPlainText(self.plainTextEditResult.toPlainText() + buildBayesReText)

        # 3.使用分类器对影像进行分类，生成一幅分类图像
        classifyReText = LandClassifier.ExecClassify(inputImageFileName,computeImagesStatisticsFileName, bayesModelTxtFileName, outputFileName)
        #self.plainTextEditResult.setPlainText(self.plainTextEditResult.toPlainText() + classifyReText)

        # 4.删除临时文件
        os.remove(computeImagesStatisticsFileName)
        os.remove(bayesModelTxtFileName)
        os.remove(bayesConfusionMatrixFileName)

    #作变化检测
    def ChangeDetection(self):
        self.updateMemberData() #更新类的数据成员

        # 1.对基准图像分类
        baseClassifyOutputName = LandClassifier.getClassifyResultName(self.baseInFileName)
        # roi中用于分类的字段名
        baseClassFieldName = self.comboBoxBaseClassField.itemData(self.comboBoxBaseClassField.currentIndex())
        self.ExecClassify(self.baseInFileName,self.baseRoiFileName,baseClassifyOutputName,baseClassFieldName)

        # 2.对第二幅栅格图像进行分类
        secondClassifyOutputName = LandClassifier.getClassifyResultName(self.secondInFileName)
        # roi中用于分类的字段名
        secondClassFieldName = self.comboBoxSecondClassField.itemData(self.comboBoxSecondClassField.currentIndex())
        self.ExecClassify(self.secondInFileName,self.secondRoiFileName,secondClassifyOutputName,secondClassFieldName)

        # 3.作波段运算
        if baseClassifyOutputName is None or secondClassifyOutputName is None:
            return
        expression = "im1b1+im2b1*100"
        changeDetectionResultName = LandClassifier.getChangeDetectionName(self.baseInFileName, self.secondInFileName)
        inputFilenameList = [baseClassifyOutputName, secondClassifyOutputName]
        bandMathReText = LandClassifier.CalcBandMath(inputFilenameList, expression, changeDetectionResultName)
        # self.plainTextEditResult.setPlainText( self.plainTextEditResult.toPlainText() + bandMathReText )

        # 4.所有地物类别的笛卡尔积
        baseNameValDict = self.getFieldsNameAndValueDict(self.baseRoiFileName)
        secondNameValDict = self.getFieldsNameAndValueDict(self.secondRoiFileName)
        baseKeys = baseNameValDict.keys()
        secondKeys = secondNameValDict.keys()
        graphicLabelDict={}
        for m, n in product(baseKeys, secondKeys):
            tempKeyAsVal =  m + "-" + n
            tempValAsKey = int( float(baseNameValDict[m]) + float(secondNameValDict[n])*100 )
            graphicLabelDict[tempValAsKey] = tempKeyAsVal

        # 绘制变化的柱状图
        showLabelValues = self.execComputeStatistics(changeDetectionResultName,graphicLabelDict.keys(),1)
        showLabelNames = graphicLabelDict.values()

        colors_ = list(six.iteritems(colors.cnames))
        # Add the single letter colors.
        for name, rgb in six.iteritems(colors.ColorConverter.colors):
            hex_ = colors.rgb2hex(rgb)
            colors_.append((name, hex_))
        clolorNamesList = [n for (n, hexVal) in colors_]

        # bar graphs
        xVal = numpy.arange(len(showLabelNames))
        vallist = showLabelValues[1]
        barwidth = 0.5
        pylab.rcParams["font.family"] = "SimHei"
        #pylab.rcParams["font.sans-serif"] = "STSong"
        pylab.xticks(xVal+barwidth/2.0,showLabelNames,rotation=60)
        pylab.bar(xVal,vallist,width=barwidth,color=clolorNamesList)
        pylab.title(u'土地利用变化图', fontproperties='SimHei')
        pylab.show()

        # 将数据写入到csv文件中
        if self.outputCSVFileName is not None or self.outputCSVFileName != "":
            csvfile = file(self.outputCSVFileName, 'wb')
            writer = csv.writer(csvfile)
            writer.writerow([s.encode("utf-8") for s in showLabelNames])
            writer.writerow(vallist)
            csvfile.close()

        # 所有操作完成，关闭对话框
        self.close()

    # 获取所有类别名称和类别值的字典
    def getFieldsNameAndValueDict(self, inputRoiImageName, classFieldName='CLASS_NAME', classFieldValue='CLASS_ID'):
        vectorlayer = QgsVectorLayer(inputRoiImageName, "inputRoiImageName", "ogr")
        classFieldName_idx = vectorlayer.fieldNameIndex(classFieldName)
        classFieldValue_idx = vectorlayer.fieldNameIndex(classFieldValue)
        allFeatures = vectorlayer.getFeatures()

        nameAndValDict={}
        for feature in allFeatures:
            name = feature.attributes()[classFieldName_idx]
            val = feature.attributes()[classFieldValue_idx]
            if name not in nameAndValDict:
                nameAndValDict[name]=val

        return nameAndValDict

    #执行计算图像统计信息的操作
    def execComputeStatistics(self, inputRasterFileName, grayValueAsKeys, readBandNo=1):
        # Open the input raster file
        # register the gdal drivers
        gdal.AllRegister()

        # Open and assign the contents of the raster file to a dataset
        dataset = gdal.Open(inputRasterFileName, GA_ReadOnly)
        if dataset is None:
            return False
        #只读取第readBandNo个波段
        oldArray = dataset.GetRasterBand(readBandNo).ReadAsArray(0, 0, dataset.RasterXSize, dataset.RasterYSize).astype(numpy.float64)

        sizeTuple = oldArray.shape
        if len(sizeTuple) == 3:
            bandNums=sizeTuple[0]   #number of bands
            imgHeight = sizeTuple[1]
            imgWidth = sizeTuple[2]
        elif len(sizeTuple) == 2:
            imgHeight = sizeTuple[0]
            imgWidth = sizeTuple[1]

        TotalPixelNum = imgHeight*imgWidth
        pixelOccurenceNumDict={}.fromkeys(grayValueAsKeys,0) #保存值标签，例如一幅图像可能有2, 4，8这三个灰度值, 那么2,4,8作为键，
        # 统计这三个灰度值的像素点共有多少个

        # 统计每个灰度值的像素点个数
        for val in oldArray.flat:
            tempValue = int(val)
            pixelOccurenceNumDict[tempValue] = pixelOccurenceNumDict[tempValue] +1

        #统计各个灰度值的百分比
        percentLabelDict={}
        for k in pixelOccurenceNumDict:
            percentLabelDict[k] = float(pixelOccurenceNumDict[k]) * 100.0 / float(TotalPixelNum)
        data = [
            pixelOccurenceNumDict.values(),
            percentLabelDict.values()
        ]
        return data